﻿using System;
using System.Diagnostics.Contracts;
using System.IO;
using OpenLS.Drawing;

namespace OpenLS.Spreadsheet.Formats.Biff.OfficeArt
{
#if DEBUG
    [ContractClass(typeof(OfficeArtBlipContract))]
#endif
    internal abstract class OfficeArtBlip : OfficeArtBStoreContainerFileBlock
    {
        public override Picture ToPicture()
        {
            Picture result = new Picture();
            result.BlipFillProperties = new BlipFillProperties();
            result.BlipFillProperties.Blip = new Blip(BLIPFileData, "." + this.Extension);
            result.BlipFillProperties.FillModeProperties = new StretchModeProperties();
            return result;
        }
#if DEBUG
        [ContractClassFor(typeof(OfficeArtBlip))]
        abstract partial class OfficeArtBlipContract : OfficeArtBlip
        {
            protected override string Extension
            {
                get
                {
                    Contract.Ensures(Contract.Result<string>() != null);
                    Contract.Ensures(GetType().ToString().ToLower().Contains(Contract.Result<string>()));
                    throw new NotSupportedException();
                }
            }
            internal override MSOBLIPTYPE Type
            {
                get
                {
                    Contract.Ensures(Contract.Result<MSOBLIPTYPE>() != MSOBLIPTYPE.msoblipERROR);
                    throw new NotSupportedException();
                }
            }
        }
#endif

        protected abstract string Extension { get; }

        private MD4Digest rgbUid1;
        private byte[] rgbUid2;
        private byte[] BLIPFileData;
        protected abstract ushort Format { get; set; }

        protected OfficeArtBlip(byte[] data)
        {
            Contract.Requires(data != null);
            this.BLIPFileData = data;
            this.rgbUid1 = MD4Digest.Compute(data);
        }

        protected OfficeArtBlip()
        {
        }

        protected override sealed int ByteLength
        {
            get
            {
                int result = 16;
                if (HasTwoIds() == UidCount.Two)
                    result += 16;
                result +=
                RestByteLength;
                result += this.BLIPFileData.Length;
                return result;

            }
        }

        protected abstract int RestByteLength
        {
            get;
        }


        protected abstract UidCount HasTwoIds();

        protected enum UidCount
        {
            One,
            Two,
        }
        protected sealed override byte Version
        {
            get { return 0; }
        }

        internal abstract MSOBLIPTYPE Type { get; }

        protected override sealed void ReadNoHeader(OfficeArtReadContext context)
        {
            Contract.Ensures(rhrecLen == context.BaseStream.Position - Contract.OldValue(context.BaseStream.Position));
#if DEBUG
            var position = context.BaseStream.Position;
#endif
            Contract.Assert(rhrecVer == 0);
            this.Format = rhrecInstance;
            UidCount hasTwoIds = HasTwoIds();
            int remainingLength = (int)(rhrecLen - 16);
            this.rgbUid1 = new MD4Digest(context.Reader);
            if (hasTwoIds == UidCount.Two)
            {
                rgbUid2 = context.ReadBytes(16);
                remainingLength -= 16;
            }
            int rest = ReadRest(context.Reader);
            Contract.Assert(rest == RestByteLength);
            remainingLength -= rest;
            Contract.Assert((remainingLength > 0));
            this.BLIPFileData = context.ReadBytes(remainingLength);
            Contract.Assert((BLIPFileData.Length == remainingLength));

#if DEBUG

            if (rgbUid2 == null)
            {
                //\\rgbUid1.Verify(BLIPFileData);
            }
            Contract.Assert(context.BaseStream.Position - position == rhrecLen);
#endif
        }
        protected sealed override void PrepareHeader(ref OfficeArtRecordHeader rh)
        {
            rh.recInstance = (ushort) this.Format;
        }
        protected sealed override void WriteNoHeader(OfficeArtWriteContext context)
        {
            UidCount hasTwoIds = HasTwoIds();
            int remainingLength = (int)(rhrecLen - 16);
            rgbUid1.Write(context.Writer);
            if (rgbUid2 != null)
            {
                context.Write(rgbUid2);
            }
            WriteRest(context.Writer);
            context.Write(this.BLIPFileData);
        }

        protected abstract void WriteRest(BinaryWriter writer);

        protected abstract int ReadRest(BinaryReader reader);

        public static OfficeArtBlip Create(Blip blip)
        {
            Contract.Ensures(Contract.Result<OfficeArtBlip>() != null);
            Contract.Ensures(Contract.Result<OfficeArtBlip>().GetDigest() != null);
            Contract.Ensures(Contract.Result<OfficeArtBlip>().Format >= 0);
            byte[] data = blip.Bytes;
            switch (blip.Extension.Replace(".", "").ToLower())
            {
                case "dib":
                    return new OfficeArtBlipDIB(data);
                case "emf":
                    return new OfficeArtBlipEMF(data);
                case "jpeg":
                    return new OfficeArtBlipJPEG(data, OfficeArtBlipJPEG.JpegFormat.RGB1);
                case "pict":
                    return new OfficeArtBlipPICT(data);
                case "png":
                    return new OfficeArtBlipPNG(data);
                case "tiff":
                    return new OfficeArtBlipTIFF(data);
                case "wmf":
                    return new OfficeArtBlipWMF(data);
                default: throw new NotSupportedException();
            }
        }

        public MD4Digest GetDigest()
        {
            Contract.Ensures(Contract.Result<MD4Digest>() != null);
            return this.rgbUid1;
        }
    }
}